__asm__(".code16gcc");

#include "loader.h"

boot_info_t boot_info;

static void show_msg(const char * msg)
{
    char c;
    while ((c = *msg++) != '\0') {
        __asm__ __volatile__(
            "mov $0x0e, %%ah\n\t"
            "mov %[ch], %%al\n\t"
            "int $0x10"::[ch]"r"(c)
        );
    }
}

/*
 * 调用BIOS E820功能检测内存布局
 */
static void detect_memory(void)
{
    show_msg("try to detect memory...");

    smap_entry_t smap_entry;
    boot_info.ram_region_count = 0;
    uint32_t signature = PAMS;
    uint32_t bytes = 0;
    uint32_t cont_id = 0;   // 第一次调用E820，ebx应当清0
    
    for (int i = 0; i < BOOT_RAM_REGION_MAX; i++) {
        smap_entry_t *entry = &smap_entry;
        __asm__ __volatile__ (
            "int $0x15"
            :"=a"(signature),"=c"(bytes),"=b"(cont_id)
            :"a"(0xe820),"b"(cont_id),"c"(24),"d"(PAMS),"D"(entry)
        );

        if (signature != PAMS) {
            show_msg("signature error.\r\n");
            return;
        }
        if (bytes > 20 && (entry->acpi & 0x01) == 0) {
            continue;
        }
        // 只获取操作系统可用内存
        if (entry->type == OS_USED) {
            boot_info.ram_region_cfg[boot_info.ram_region_count].start = entry->base_l;
            boot_info.ram_region_cfg[boot_info.ram_region_count].size = entry->length_l;
            boot_info.ram_region_count++;
        }
        if (cont_id == 0)
            break;
    }

    show_msg("ok.\r\n");
}

uint16_t gdt_table[][4] = {
    /*
     * 空描述符
     */
    {0, 0, 0, 0},

    /*
     * 段界限0xfffff，段基地址0x00000000
     * P=1 DPL=00 S=1
     * X=1（代码段） C=0 R=1 A=0
     * G=1 D=1（32位尺寸） L=0 AVL=0
     * 粒度为1，则实际段界限=0xfffff * 0x1000 + 0xfff = 0xffffffff，即整个4GB内存
     */
    {0xffff, 0x0000, 0x9a00, 0x00cf},

    /*
     * 段界限0xfffff，段基地址0x00000000
     * P=1 DPL=00 S=1
     * X=0 E=0 W=1 A=0
     * G=1 B=1 L=0 AVL=0
     * 粒度为1，则实际段界限=0xfffff * 0x1000 + 0xfff = 0xffffffff，即整个4GB内存
     */
    {0xffff, 0x0000, 0x9200, 0x00cf}
};

static void enter_protected_mode(void)
{
    cli();

    // 开启A20地址线
    uint8_t v = inb(0x92);
    outb(0x92, v | 0x02);

    lgdt((uint32_t)gdt_table, sizeof gdt_table);

    // CR0 PE位置1，开启保护模式
    uint32_t cr0 = read_cr0();
    write_cr0(cr0 | 0x01);

    far_jmp(SELECTOR_CODE, (uint32_t)_protected_mode_entry);
}

void loader_entry(void)
{
    show_msg(".....loading.....\r\n");
    detect_memory();
    enter_protected_mode();
    for(;;) {}
}